﻿using System;
using System.Collections.Generic;
using System.Linq;

namespace BorgExt.Behaviors
{
    public class CreateAndPlugBehavior : Behavior<Pair<Slot, PlugType>>
    {
        public CreateAndPlugBehavior(IRule<Pair<Slot, PlugType>> rule)
            : base(rule)
        { }

        public override void Bind(Slot slot)
        {
            base.Bind(slot);
            TypeStore.PluginAdded += PluginAdded;
            TypeStore.PluginRemoved += PluginRemoved;

            if (!slot.IsOpen())
                return;

            foreach (var plugType in TypeStore.GetPlugTypes(slot.Name))
                Rule.AddCandidate(new Pair<Slot, PlugType>(slot, plugType));

            foreach (var plug in slot.PluggedPlugs)
                Rule.Composed(new Pair<Slot, PlugType>(slot, plug.PlugType));
        }

        public override bool CanPlug(CompositionEventArgs args, out LogEventArgs log)
        {
            return Rule.CanCompose(new Pair<Slot, PlugType>(args.Slot, args.Plug.PlugType), out log);
        }

        public override bool CanUnplug(CompositionEventArgs args, out LogEventArgs log)
        {
            return Rule.CanDecompose(new Pair<Slot, PlugType>(args.Slot, args.Plug.PlugType), out log);
        }

        public override void Closed(SlotEventArgs args)
        {
            foreach (var plugType in TypeStore.GetPlugTypes(args.Slot.Name))
                Rule.RemoveCandidate(new Pair<Slot, PlugType>(args.Slot, plugType));
        }

        protected override void Compose(Pair<Slot, PlugType> pair)
        {
            var first = pair.First;
            var second = pair.Second;
            
            var extension = first.Shared
                ? Composer.GetShared(second.ExtensionType)
                : Composer.Create(second.ExtensionType);
            
            first.Plug(extension.Plugs[second.Name]);
        }

        protected override void Decompose(Pair<Slot, PlugType> pair)
        {
            var first = pair.First;
            var plugType = pair.Second;

            foreach (var plug in
                from plug in first.PluggedPlugs
                where plug.PlugType == plugType
                select plug)
                first.Unplug(plug);
        }

        public override void Opened(SlotEventArgs args)
        {
            foreach (var plugType in TypeStore.GetPlugTypes(args.Slot.Name))
                Rule.AddCandidate(new Pair<Slot, PlugType>(args.Slot, plugType));
        }

        public override void Plugged(CompositionEventArgs args)
        {
            Rule.Composed(new Pair<Slot, PlugType>(args.Slot, args.Plug.PlugType));
        }

        public override void Plugging(CompositionEventArgs args)
        {
            Rule.Composing(new Pair<Slot, PlugType>(args.Slot, args.Plug.PlugType));
        }

        private void PluginAdded(PluginEventArgs args)
        {
            if (BehaviorSlot != null)
            {
                if (BehaviorSlot.IsOpen())
                {
                    Func<ExtensionType, IEnumerable<PlugType>> selector =
                        extensionType =>
                        from plugType in extensionType.PlugTypes
                        where plugType.Name == BehaviorSlot.Name
                        select plugType;

                    foreach (var plugType in args.Plugin.ExtensionTypes.SelectMany(selector))
                        Rule.AddCandidate(new Pair<Slot, PlugType>(BehaviorSlot, plugType));
                }
            }
            else
            {
                foreach (var extensionType in args.Plugin.ExtensionTypes)
                    foreach (var plugType in extensionType.PlugTypes)
                        foreach (var slot in
                            from slot in InstanceStore.GetSlots(plugType.Name)
                            where slot.IsOpen()
                            select slot)
                            Rule.AddCandidate(new Pair<Slot, PlugType>(slot, plugType));
            }
        }

        private void PluginRemoved(PluginEventArgs args)
        {
            if (BehaviorSlot != null)
            {
                Func<ExtensionType, IEnumerable<PlugType>> selector =
                    extensionType =>
                    from plugType in extensionType.PlugTypes
                    where plugType.Name == BehaviorSlot.Name
                    select plugType;

                foreach (var plugType in args.Plugin.ExtensionTypes.SelectMany(selector))
                    Rule.RemoveCandidate(new Pair<Slot, PlugType>(BehaviorSlot, plugType));
            }
            else
                foreach (var extensionType in args.Plugin.ExtensionTypes)
                    foreach (var plugType in extensionType.PlugTypes)
                        foreach (var slot in
                            from slot in InstanceStore.GetSlots(plugType.Name)
                            where slot.IsOpen()
                            select slot)
                            Rule.RemoveCandidate(new Pair<Slot, PlugType>(slot, plugType));
        }

        public override void Unbind(Slot slot)
        {
            TypeStore.PluginAdded -= PluginAdded;
            TypeStore.PluginRemoved -= PluginRemoved;
            
            base.Unbind(slot);
        }

        public override void Unplugged(CompositionEventArgs args)
        {
            Rule.Decomposed(new Pair<Slot, PlugType>(args.Slot, args.Plug.PlugType));
        }

        public override void Unplugging(CompositionEventArgs args)
        {
            Rule.Decomposing(new Pair<Slot, PlugType>(args.Slot, args.Plug.PlugType));
        }
    }
}

